#!/usr/bin/perl
## Simple stack-based RPN calculator for linux version numbers.
## Usage:
##
##    scripts/vers [operand|operation ...]
##
## Operations all start with '-', everything else is an operand
## and is pushed on the stack as-is. 
## When all arguments have been processed, the content of the
## top of the stack is printed on stdout and the script ends.
##
## Available operations:

sub checkversion
{
	my $v = shift;
	my ($may, $min, $sub) = split /\./, $v;

	if ($may < 2 || $min < 0 || $sub < 0 ||
	   ($may == 2 && $min != 6 && !($sub >= 32 && !$sub <= 39)) ||
           ($may == 3 && $min > 19)) {
		die "Bad version $v";
	}
}

sub conv
{
	my $v = shift;

	return sprintf "%x%02x%02x", (split /\./, $v);
}


sub rconv
{
	my $v = shift;

	$v =~ /(.*)(..)(..)$/;
	if ($1 > 2 && (hex $3) == 0) {
		return sprintf "%d.%d", (hex $1), (hex $2);
	}
	return sprintf "%d.%d.%d", (hex $1), (hex $2), (hex $3);
}

sub next
{
	my $v = shift;
	my ($may, $min, $sub) = split /\./, $v;

	if ($may == 2) {
	       	if ($sub < 39) {
			return "2.6." . ($sub + 1);
		} else {
			return "3.0";
		}
	} elsif ($may == 3) {
		if ($min < 19) {
			return "3." . ($min + 1);
		} else {
			return "4.0";
		}
	} elsif ($may == 4) {
		if ($min < 20) {
			return "4." . ($min + 1);
		} else {
			return "5.0";
		}
	} else {
		return "$may." . ($min + 1);
	}
}

sub prev
{
	my $v = shift;
	my ($may, $min, $sub) = split /\./, $v;

	if ($min == 0) {
		if ($may == 3) {
			return "2.6.39";
		} elsif ($may == 4) {
			return "3.19";
		} elsif ($may == 5) {
			return "4.20";
		} else {
			die "Unknown version: $v";
		}
	} else {
		if ($may == 2) {
			if ($sub == 32) {
				die "Out of range: prev($v)";
			} else {
				return "2.6." . ($sub - 1);
			}
		} else {
			return "$may." . ($min - 1);
		}
	}
}

@ARGV or do { system("scripts/help $0"); exit 1; };

for (@ARGV) {
##
##  -b	(nullary) suppress normal output. On exit, return 1
## 	if stack top is "false", 0 otherwise.
	/^-b$/  && do {
		$silent=1;
		next;
	};
##
##  -c	(unary) convert from dot to fixed notation
	/^-c$/	&& do {
		$v = pop @stack;
		&checkversion($v);
		push @stack, &conv($v);
		next;
	};
##
##  -C	(unary) convert from fixed to dot notation
	/^-C$/	&& do {
		$v = pop @stack;
		push @stack, &rconv($v);
		next;
	};
##
##  -i	(unary) increment version number
##	(must be in dot notation)
	/^-i$/ && do {
		$v = pop @stack;
		&checkversion($v);
		push @stack, &next($v);
		next;
	};
##
##  -d	(unary) decrement version number
##	(must be in dot notation)
	/^-d$/ && do {
		$v = pop @stack;
		&checkversion($v);
		push @stack, &prev($v);
		next;
	};
##
##  -s	(unary) assume the stack top is a
## 	string containing several fields separated
## 	by '--'. Replace the stack top with these
## 	fields (last on top)
	/^-s$/ && do {
		$v = pop @stack;
		push @stack, split /--/, $v;
		next;
	};
##
##  -SN	(N-ary) pop N elements from the stack,
## 	join them using '--' as a separator
## 	(top as last) and push the resulting 
## 	string
	/^-S(\d+)$/ && do {
		$n = $1;
		@t = @stack[-$n..-1];
		while ($n--) {
			pop @stack;
		}
		push @stack, (join '--', @t);
		next;
	};
##
##  -p	(unary) pop
	/^-p$/ && do {
		pop @stack;
		next;
	};
##
##  -l	(binary) push "true" if first version
## 	number is stricly less then second version
## 	number (versions in fixed notation)
##
##  -L	(binary) like -l, but for version numbers
## 	in dot notation
	/^-[lL]$/ && do {
		$v1 = pop @stack;
		$v2 = pop @stack;
		/^-L$/ && do {
			&checkversion($v1);
			&checkversion($v2);
			$v1 = &conv($v1);
			$v2 = &conv($v2);
		};
		push @stack, (($v2 lt $v1) ? "true" : "false");
		next;
	};
##
##  -a	(binary) logical and. Arguments must be
## 	either "true" or "false".
	/^-a$/ && do {
		$v1 = pop @stack;
		$v2 = pop @stack;
		push @stack, (($v1 eq "true" && $v2 eq "true") ? "true" : "false");
		next;
	};
##
##  -n	(unary) logical not. Argument must be
## 	either "true" or "false".
	/^-n$/ && do {
		$v1 = pop @stack;
		push @stack, (($v1 eq "true") ? "false" : "true");
		next;
	};
	push @stack, $_;
}
$v = pop @stack;
if ($silent) {
	exit ($v eq "false");
}
print "$v\n";
